Skip to content

Conversation

@silver-eunjoo
Copy link
Collaborator

@silver-eunjoo silver-eunjoo commented Jul 19, 2025

🛰️ Issue Number

🪐 작업 내용

0. 타이머 소개

  • 방마다 독립적인 타이머가 필요하기 때문에 Room에 타이머를 추가했습니다.
  • ScheduledExecutorService scheduler 은 예약 작업을 실행시켜주는 스레드 서비스입니다.
    • scheduler.schedule()을 호출하면, 일정 시간 이후에 특정 로직을 실행하도록 예약할 수 있습니다.
  • ScheduledFuture<?> timer는 예약된 타이머 작업을 취소하거나 상태를 체크할 수 있게 해주는 객체입니다.
    • scheduler로 예약해놓은 작업을 막아주기 위해 timer.cancel()과 같이 타이머를 취소할 수 있습니다.
  • 또한, 타이머 기능을 다룰 수 있도록 TimerService로 분리하였습니다.

1. 게임 시작 후 게임 진행 흐름 설명

1) 게임 시작

  • 게임을 시작하면서, GAME_START와 함께, QUESTION_START로 첫 문제를 보내줍니다.
  • 첫 문제가 시작하기 전 delay는 5초로 두었습니다.
  • QUESTION_START에는 서버가 응답을 보내주는 현재 시간 + 5초고, 클라이언트에서는 시간 보정 후, 해당 timestamp에 문제를 시작하게 됩니다.

2) 게임 진행 + 타임 아웃

  • 타이머는 응답을 내려준 시점에서 delay 시간 (시작 시 : 5초, 이어지는 문제일 시 : 3초) + 문제의 제한 시간에 예약을 걸어둡니다.
  • 만약, 해당 시간이 끝났을 경우, 문제를 맞힌 사람이 없다고 판단하며, handleTimeout() 메서드가 자동 호출됩니다.
  • handleTimeout()에서는 한 문제가 종료되었기 때문에 QUESTION_RESULTSYSTEM_NOTICE를 브로드캐스팅해줍니다.
    • 실시간 랭킹에는 변동이 없을 것이기 때문에 응답에서 제외했습니다.
  • 마지막 라운드일 경우, 게임 종료 로직이 들어가야 합니다. (이 부분은 비워뒀습니다!)
  • 마지막 라운드가 아닐 경우, 다시 타이머를 시작하고, QUESTION_START를 다시 브로드캐스팅합니다. (이어지는 문제이기 때문에, delay는 3초로 두었습니다.)

3) 게임 진행 + 채팅으로 정답자있을 경우

  • 현재 진행중인 타이머를 취소합니다. (예약걸어놓은 handleTimeout이 실행되지 않도록!)
  • 이 경우에도, 현재 마지막 라운드인지 확인 후, 마지막 라운드일 경우, 게임 종료 로직이 들어가야 합니다. (아직 미구현)
  • 마지막 라운드가 아닐 경우, 다시 타이머를 시작하고, QUESTION_START를 다시 브로드캐스팅합니다. (이어지는 문제이기 때문에, delay는 3초로 두었습니다.)

4) RoomMapper에서 수정한 부분

  • RoomMapper에서 상수로 기본 시간 제한이 있고, 이걸 이용해서 toGameSettingResponse를 만들더라구요 ! 그래서 GameSetting에 필드에 설정된 기본값이 사용되지 않는 것 같아 지워두었습니다.
    • 이 부분은 이후 게임 설정 변경 부분에서 필요할 때 다시 리팩토링하는 것이 좋을 것 같습니다.
  • toQuestionResultResponse에서 파라미터로 ChatMessage를 받고 있었는데, 타임아웃의 경우 ChatMessage 정보가 없기 때문에, 파라미터를 String correctUser를 받도록 변경했습니다. -> 타임아웃 시, 닉네임 빈문자열

2. 테스트 결과

  • 타임아웃 테스트는 문제 제한 시간을 5초
  • 채팅 정답 테스트는 문제 제한 시간을 10초로 두고 테스트했습니다.
  • 로그 시간 보기가 까다로울 것 같아 표시는 해두었는데, 좀 깨지는 것 같기두 하네요.. 🫠

1) 타임아웃

<서버>
스크린샷 2025-07-20 오전 2 23 20

<클라이언트>
스크린샷 2025-07-20 오전 1 59 46

2) 채팅으로 정답

<클라이언트>
Image-1

<서버>
Image-2

📚 Reference

✅ Check List

  • 코드가 정상적으로 컴파일되나요?
  • 테스트 코드를 통과했나요?
  • 테스트 해봤나요?
  • merge할 브랜치의 위치를 확인했나요?
  • Label을 지정했나요?

@silver-eunjoo silver-eunjoo self-assigned this Jul 19, 2025
@silver-eunjoo silver-eunjoo added the enhancement New feature or request label Jul 19, 2025
@silver-eunjoo silver-eunjoo linked an issue Jul 19, 2025 that may be closed by this pull request
3 tasks

private String getDestination(Long roomId) {
return "/sub/room/" + roomId;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getDestinatnionpublic static 으로 변경해서 util로 분리하는건 어떨까요?
현재 roomService, gameService에서 중복인 메소드라서요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

util 분리가 좋을 것 같습니다 ! 수정하겠습니다 !

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

흠.. 얘도 WebSocketUtils에서 관리하면 될까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

우선은 WebSocketUtils로 분리해두었습니다 !

room.increaseCurrentRound();

// 타이머 추가하기
timerService.startTimer(room, CONTINUE_DELAY);
Copy link
Collaborator

@sehee123 sehee123 Jul 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

261 라인부터 272라인까지 혹시 아래 if문 안에서 실행되는건가요?
if (answer.equals(chatMessage.message())) {
현재로선 메세지 send 후 } 중괄호가 끝난 것 처럼 보입니다!
정답 처리시에만 increaseMessageSend.send를 실행하게 해놓아서 타이머 로직 또한 이 if문에 포함되어야하지 않을까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

헉! 리팩토링 전 코드로 return문이 실행된다고 생각했네요..!! 감사합니다 ! 이 부분도 수정하겠습니다 ! :)

handleTimeout(room);
},
delaySec + room.getGameSetting().getTimeLimit(),
TimeUnit.SECONDS);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오홍.. 재연결 유예시간 로직 구현할 때 참고해서 구현해봐야겠군요 👀


private String getDestination(Long roomId) {
return "/sub/room/" + roomId;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기도 있군요.
확실히 Util로 분리해야할것 같습니다.. 🥕


private Long quizId;
private Integer round; // 게임 변경 시 해당 게임의 총 문제 수로 설정
private int timeLimit = 60;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이부분 전 캐치 못하고있었는뎁 감사합니다 ~!

import io.f1.backend.domain.game.dto.response.GameStartResponse;
import io.f1.backend.domain.game.dto.response.QuestionStartResponse;

public record GameStartData(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이건 어디서 쓰이는걸까요?.?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

어머 삭제했는데,, 충돌 해결하면서 제대로 안 사라졌나봅니다... 지우겠습니닷 !

ofPlayerEvent(chatMessage.nickname(), RoomEventType.ENTER));
ofPlayerEvent(chatMessage.nickname(), RoomEventType.CORRECT_ANSWER));

timerService.cancelTimer(room);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[L2-변경협의]
cancelTimer 직전 시점에 timeout으로 인해 handleTimeout이 호출되는 edge case가 존재할 것 같습니다.
이 경우를 대비해 cancelTimer 메서드에 timer가 이미 정지된 상태인지 알려주는 boolean return 값을 부여하고,
이를 통해 조건문 처리를 하면 좋을 것 같습니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cancelTimer() 호출 시점을 chat() 메서드 초반에 호출하는 방식으로 리팩토링해보겠습니다.

근데, timer가 이미 정지된 상태인지 알려주는 boolean return 값이 왜 필요한지 잘 모르겠는데, 혹시 더 설명해주실 수 있으실까요?

Copy link
Collaborator

@dlsrks1021 dlsrks1021 Jul 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cancelTimer 호출 시 timer가 정지 상태라는 것은 handleTimeout이 먼저 호출됐다는 뜻일 겁니다.
그렇다면 handleTimeout 로직에 따라 Question Result와 System Notice가 브로드캐스트 된 이후 새로운 timer의 start와 Question Start를 진행하는 과정을 거치고 있을 텐데, chat 메서드 내의 cancelTimer 호출 이후 같은 로직을 진행하면 오동작을 일으킬 가능성이 존재할 것 같습니다.
따라서 boolean return 값을 통해 분기 처리를 해서 같은 로직을 진행하지 않도록 하는게 좋을 것 같다는 의견이었습니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오.. 그렇군요 ! 이해했습니다. 리뷰를 읽고 생각을 해봤는데,, 채팅으로 정답을 맞혔을 때, 요청을 처리하는 도중 타임아웃이 된 경우를 생각하다보니,, 동시성까지 생각해야 해서 생각이 많아졌습니다..! 혹시 이 부분은 좀 더 생각해보고 다음 PR에 반영하도록 하겠습니다 !!! 감사합니다 ! :)

Copy link
Collaborator

@LimKangHyun LimKangHyun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dev브랜치에 머지된 #90 번 PR 내용이 RoomService에 적용이 되어있지 않은것 같습니다!

@silver-eunjoo
Copy link
Collaborator Author

dev브랜치에 머지된 #90 번 PR 내용이 RoomService에 적용이 되어있지 않은것 같습니다!

어..! 왜지.. 이 부분 보고 수정하겠습니다 ! 감사합니다 !

} else if (roomEventType == RoomEventType.CORRECT_ANSWER) {
message = " 님 정답입니다 !";
} else if (roomEventType == RoomEventType.TIMEOUT) {
message = "땡 ~ ⏰ 제한 시간 초과!";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

공지 메시지로 쓰이는 문자열을 enum으로 관리하면 편할 것 같습니다 !

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인했습니다 ! 이 부분 수정해보겠습니다 !

Copy link
Collaborator

@LimKangHyun LimKangHyun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다! 퀴즈때문에 이 부분도 언젠가는 수정이 필요하겠네요...

Copy link
Collaborator

@jiwon1217 jiwon1217 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다 ! PR 내용이 알차네요 ㅎㅎ

Copy link
Collaborator

@sehee123 sehee123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다! PR 멋쪄요 .. 👀

@silver-eunjoo silver-eunjoo merged commit 4c2cd3a into dev Jul 21, 2025
2 of 3 checks passed
@silver-eunjoo silver-eunjoo deleted the feat/79 branch July 21, 2025 13:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] 게임 시작 후 문제 시작 + 타이머 기능 추가

6 participants